package tx.os3.clientserver.client.connection;

import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;

import tx.os3.clientserver.client.model.FileListModel;
import tx.os3.clientserver.client.model.FileRepresentation;
import tx.os3.clientserver.client.model.ServerModel;
import tx.os3.clientserver.client.model.ServerModel.ServerStatus;

public class ServerConnectionProtocolAdapter implements ProtocolAdapter {

    private ServerConnection connection;
    private ServerModel serverModel;
    private FileListModel fileListModel;
    private ExecutorService executor;

    public ServerConnectionProtocolAdapter(ServerModel serverModel,
            FileListModel fileListModel) {
        if(serverModel == null) {
            throw new IllegalArgumentException("ServerModel cannot be null.");
        }
        if(fileListModel == null) {
            throw new IllegalArgumentException("FileListModel cannot be null.");
        }
        this.serverModel = serverModel;
        this.fileListModel = fileListModel;
        this.executor = Executors.newFixedThreadPool(1);
        boolean connected = this.connect();
    }

    @Override
    public void getServerInfo() {
        if(this.connection != null) {
            this.executor.execute(new Runnable() {
                @Override
                public void run() {
                    String result = connection
                            .write(ProtocolAdapter.COMMAND_INFO);
                    serverModel.setServerInfo(result);
                }
            });
        }
    }

    @Override
    public void disconnect() {
        if(this.connection != null) {
            this.executor.execute(new Runnable() {
                @Override
                public void run() {
                    connection.write(COMMAND_DISCONNECT);
                    connection.close();
                    connection = null;
                    serverModel.setStatus(ServerStatus.DISCONNECTED);
                }
            });
        }
    }

    @Override
    public boolean connect() {
        if(this.connection == null) {
            try {
                this.connection = ServerConnection.getInstance();
                this.serverModel.setStatus(ServerStatus.CONNECTED);
            }
            catch(UnknownHostException e) {
                this.serverModel.setStatus(ServerStatus.DISCONNECTED);
            }
            catch(IOException e) {
                this.serverModel.setStatus(ServerStatus.DISCONNECTED);
            }
        }
        return this.serverModel.getStatus() == ServerStatus.CONNECTED;
    }

    @Override
    public void listDir(final String directory) {
        if(this.connection != null) {
            this.executor.execute(new Runnable() {
                @Override
                public void run() {
                    String result = connection
                            .write(ProtocolAdapter.COMMAND_DIR + " \""
                                    + directory + "\"");
                    if(result != null) {
                        fileListModel.fromString(result);
                    }
                }
            });
        }
    }

    @Override
    public void put(final File file) {
        if(file == null) {
            throw new IllegalArgumentException("File to put cannot be null.");
        }
        this.executor.execute(new Runnable() {
            @Override
            public void run() {
                String fileName = file.getName();
                String replacePath = new File(FileListModel.WEBDIR
                        + FileListModel.ROOT).getAbsolutePath();
                if(file.getAbsolutePath().startsWith(replacePath)) {
                    fileName = file.getAbsolutePath().substring(
                            replacePath.length());
                }
                byte[] fileContent = null;
                try {
                    // Set up data to write
                    String writeData = ProtocolAdapter.COMMAND_PUT + " \""
                            + fileName + "\"";
                    if(!file.isDirectory()) {
                        fileContent = FileUtils.readFileToByteArray(file);
                        writeData += " " + fileContent.length;
                    }
                    connection.write(writeData, fileContent);
                }
                catch(IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void delete(final String fileName) {
        if(fileName == null) {
            throw new IllegalArgumentException("File name cannot be null.");
        }
        this.executor.execute(new Runnable() {
            @Override
            public void run() {
                connection.write(ProtocolAdapter.COMMAND_DELETE + " \""
                        + fileName + "\"");
            }
        });
    }

    @Override
    public void rename(final String fileName, final String newFileName) {
        if(fileName == null || newFileName == null) {
            throw new IllegalArgumentException(
                    "File name and new file name cannot be null.");
        }
        this.executor.execute(new Runnable() {
            @Override
            public void run() {
                connection.write(ProtocolAdapter.COMMAND_RENAME + " \""
                        + fileName + "\" \"" + newFileName + "\"");
            }
        });
    }

    @Override
    public void get(final String fileName) {
        if(fileName == null) {
            throw new IllegalArgumentException("File name cannot be null.");
        }
        this.executor.execute(new Runnable() {
            @Override
            public void run() {
                byte[] result = connection
                        .writeBinaryResult(ProtocolAdapter.COMMAND_GET + " \""
                                + fileName + "\"");
                File file = new File(FileListModel.WEBDIR + fileName);
                try {
                    FileUtils.writeByteArrayToFile(file, result);

                    file.createNewFile();
                }
                catch(IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void sync() {
        final String workingDir = this.fileListModel.getCurrentDir();
        if(workingDir == null) {
            return;
        }

        this.executor.execute(new Runnable() {
            @Override
            public void run() {
                ServerConnectionProtocolAdapter.this.syncDirectory(workingDir);

                ServerConnectionProtocolAdapter.this.listDir(workingDir);
            }
        });
    }

    public void syncDirectory(final String directory) {
        // Refresh file list model
        this.listDir(directory);
        List<FileRepresentation> serverFiles = FileListModel.getInstance()
                .getFiles();
        // Get files in client webdir
        File[] clientFiles = new File(FileListModel.WEBDIR + directory).listFiles();
        for(File clientFile : clientFiles) {
            boolean foundOnServer = false;
            boolean newerOnClient = false;
            Iterator<FileRepresentation> serverIterator = serverFiles
                    .iterator();
            while(serverIterator.hasNext()) {
                FileRepresentation serverFile = serverIterator.next();
                if(clientFile.getName().equals(serverFile.getFileName())) {
                    foundOnServer = true;
                    if(!clientFile.isDirectory()) {
                        // Same file name, compare date modified
                        newerOnClient = clientFile.lastModified() > serverFile
                                .getLastModified().getTime();
                    }
                    // Remove current object from list so iterating over this
                    // list later will speed up
                    serverIterator.remove();
                    break;
                }
            }
            // If not found on server, put this file
            if(!foundOnServer || newerOnClient) {
                ServerConnectionProtocolAdapter.this.put(clientFile);
            }
        }

        // Iterate over server files to see which files should be deleted
        for(FileRepresentation serverFile : serverFiles) {
            ServerConnectionProtocolAdapter.this.delete(serverFile.getFileName());
        }


        for(File dir : clientFiles) {
            if(dir.isDirectory()) {
                ServerConnectionProtocolAdapter.this.syncDirectory(directory + "/" + dir.getName());
            }
        }
    }
}
